#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cassert>
#include <unordered_map>
#include <functional>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

#include "Protocol.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    // typedef std::function<bool(const HttpRequest &, HttpResponse &)> func_t;
    using func_t = std::function<bool(const HttpRequest &, HttpResponse &)>;

    class HttpServer
    {
    public:
        HttpServer(func_t func, const uint16_t &port = gport)
            : _listensock(-1), _port(port), _func(func) {}

        void initServer()
        {
            // 1. 创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                exit(SOCKET_ERR);
            }

            // 测试3：
            // 1.2 端口复用
            // 在创建 _listensock 成功之后设置端口复用，来测试./httpserver 8080主动关闭后，由此处于TIME_WAIT状态，
            // 引起的8080 端口被占用，不能立即./httpserver 8080的问题是否能够立即启动了
            // 一个终端用来启动./httpserver 8080,另一个终端用来作为一个服务器执行telnet 1.117.155.7(公网IP) 8080(启动的server的端口)
            // server窗口ctrl + c,使得server主动退出，client自动也退出了 --> 在立即 ./httpserver 8080 是不能立即启动的
            // 设置了下面的端口复用的话，是可以立即启动了的。
            int opt = 1;
            setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));


            // 2. bind绑定自己的网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                exit(BIND_ERR);
            }

            // 3. 设置socket 为监听状态
            if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面在填这个坑
            {
                exit(LISTEN_ERR);
            }
        }

        void start()
        {
            for (;;)
            {
                // 4. server 获取新链接
                // sock, 和client进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    continue;
                }

                // version 2 多进程版(2)
                pid_t id = fork();
                if (id == 0) // child
                {
                    close(_listensock);
                    if (fork() > 0)
                        exit(0);
                    // todo
                    HandlerHttp(sock);

                    // 测试1：cleint 主动断开连接的一个测试
                    // 如果我们把正常的做法 close(sock) 注释来测试四次挥手被动的一方(我们这里设置为server)，处于CLOSE_WAIT状态
                    // 1. 一个窗口启动服务器./httpserver 8080, 另一个窗口使用查看服务的命令 netstat -natp 可以看到我们的 ./httpserver 服务，这个./httpserver 服务的状态就是我们设置的 LISTEN 状态的哦
                    // 2. 再开一个窗口用另一个账号sudo telnet 1.117.155.7 8080 连接我们的服务器的时候，
                    // 我们再查看服务的命令 netstat -natp 可以看到我们就有了两个 ./httpserver 的进程了，因为我们使用的是多进程的方式的哦
                    // 3. 我们再在telnet 1.117.155.7 8080窗口下断开连接：ctrl + ], 会出现telnet>, 我们再输入 quit 回车，client就主动断开连接了。
                    // 4. 我们再netstat -natp查看，可以看到之前断开连接的./httpserver服务进程的 State 是 CLOSE_WAIT 状态的哦
                    // (还能查看到这个服务进程就说明，这个服务还没有和我们的服务器断开连接的哦 --> 会一直存在server可以给client发送数据)。
                    // 5. 我们再主动 ctrl+c 关闭./httpserver 8080, 再netstat -natp查看的时候，关于 ./httpserver 的服务器就都没有 --> os 把我们关闭了一些资源
                    //
                    // 6. 服务器有大量的 CLOSE_WAIT 状态的进程的话，说明我们的 server是有问题的哦
                    // close(sock);
                    // exit(0);

                    
                    // 测试2：cleint 主动断开连接的一个测试
                    // 1. 我们 ./httpserver 8080启动服务器
                    // 2. 再卡一个窗口使用一个监控脚本 while : ; do netstat -natp | grep httpserver; sleep 1; echo "------"; done
                    // 3. 因为我们的client，server端的代码都没有问题 --> 
                    // 4. 再开一个窗口,登录另一个账户，启动 telnet 1.117.155.7 8080 作为一个client连接我们的 server
                    // 5. 再开一个窗口也启动一个监控脚本  while : ; do netstat -natp | grep netstat; sleep 1; echo "------"; done
                    // 6. 可以看到，再主动断开连接的一方(这里是client)最终是 time_wait 状态，最后又关闭了。
                    // 7. 被动的一方 server先维持一段时间的 CLOSE_WAIT，至于为什么我们的.md文件里面有解释的哦。
                    close(sock);
                    exit(0);

                    // 测试3：server 主动断开连接的一个测试
                    // 在创建 _listensock 成功之后设置端口复用，来测试./httpserver 8080主动关闭后，由此处于TIME_WAIT状态，
                    // 引起的8080 端口被占用，不能立即./httpserver 8080的问题是否能够立即启动了
                
                }
                close(sock);

                // father
                waitpid(id, nullptr, 0);
            }
        }

        void HandlerHttp(int sock)
        {
            // 1. 读到完整的http请求
            // 2. 反序列化
            // 3. httprequst, httpresponse, _func(req, resp)
            // 4. resp序列化
            // 5. send
            // char buffer[1024];
            // HttpRequest req;   // 读取请求
            // HttpResponse resp; // 处理好了之后再发送响应
            // // 大概率我们直接就能读取到完整的http请求
            // size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
            // if (n > 0)
            // {
            //     buffer[n] = 0;
            //     req.inbuffer = buffer; // 在类外调用到成员变量 --> public
            //     // 反序列化 req
            //     req.parse();
            //     // funcs[req.path](req, resp);
            //     // 处理一下，序列化 resp
            //     _func(req, resp);
            //     // 把序列化的 resp 给发送出去
            //     send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
            // }


            /*---------------------------------------------------------------------------------------------------------------------*/
            // 进行下面的测试的时候，上面的处理client的代码就注释server不做什么了
            // 测试1：
            // 因为是多进程的方式，所以我们把上面的处理代码给注释了。这里提供一个死循环就可以了，什么都不做
            // while(true){
            //     sleep(1);
            // }

            // 测试2：
            // sleep(5);
            // char buffer[1024];
            // size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
            // sleep(5);

            // 测试3：
            // 因为是多进程的方式，所以我们把上面的处理代码给注释了。这里提供一个死循环就可以了，什么都不做
            while(true){
                sleep(1);
            }
        }

        // 添加 std::string servicename 对应的方法 func_t cb
        // void registerCb(std::string servicename, func_t cb){
        //     funcs.insert(make_pair(servicename, cb));
        // }

        ~HttpServer() {}

    private:
        int _listensock; // 不是用来进行数据通信的，它是用来监听链接到来，获取新链接的！
        uint16_t _port;
        func_t _func;
        // std::unordered_map<std::string, func_t> funcs;
    };
} // namespace server
